# -*- coding: utf-8 -*-

import os
import re
import sys
import json
import docx
import ctypes
import win32ui
from win32com import client as wc

def readerDocx(filePath):
    w = docx.Document(filePath)
    data = ''
    for p in w.paragraphs:
        data += p.text + '\n'

    return data


# 判断当前行是否为选项行
def isOptionsLine(content):
    content = content.strip()
    return bool(re.search(r'^[A-Y][\.．、\s]{0,2}[^\.．、]+\s*(?=(?:[B-Z][\.．、\s]{0,2}[^\.．、]+\s*)|$)', content))


def get_now_option_regex(now_index):
    now_char = chr(now_index + 65)
    next_char = chr(now_index + 66)
    return re.compile(now_char + '[\.．、\s]{0,2}[^.．、\s]+?\S*?(?=(?:\s*?' + next_char + '[\.．、\s]{0,2}[^.．、\s]+?\S*?)|(?:\s*$))')


def is_line_end(line_content, part_content):
    return re.search('^.*' + part_content + '\s*$', line_content)


def decode(data, title):

    data = data.split('\n')

    # 大题、题型、小题的匹配规则 + 等价于 {1,}
    classRegex = re.compile(r'^\s*(一|二|三|四|五|六|七|八|九|十|百|千|万)+、[\u4e00-\u9fa5]+')
    typeRegex = re.compile(r'^\s*(\(|（)(一|二|三|四|五|六|七|八|九|十|百|千|万){1,3}(\)|）)[\u4e00-\u9fa5]+_{1}[\u4e00-\u9fa5]+')
    TiGan_Regex = re.compile(r'^\s*(\||｜|丨|〡)题干结束(\||｜|丨|〡)\s*$')
    trueAnswer = re.compile(r'[A-Z对错]')
    flag_TiXing = re.compile(r'^\s*[0-9对错]|([A-Z](\s{0,5}、?\s{0,5}[A-Z]){0,5})\s*$')  # 匹配一段题目的结尾那一行

    allTopic = {
        'title': title,
        'class': {},
        'data': []
    }

    flag_id = 0
    flag_class = ''
    flag_tiXing = ''

    TiGan_range = {}


    # 确定每一题的范围
    for index, p in enumerate(data):
        if flag_class in TiGan_range and \
          TiGan_range[flag_class] and \
          index <= TiGan_range[flag_class][-1][-1]:  # 如果当前行已经被前面确定为某一题的范围，就跳过当前行
            # print('已经处理过%s，跳过' % index, p)
            continue

        mo = classRegex.search(p) if classRegex.search(p)\
            else typeRegex.search(p) if typeRegex.search(p)\
            else TiGan_Regex.search(p) if TiGan_Regex.search(p)\
            else False

        # 大题
        if classRegex.search(p):
            flag_class = re.split(r'[、\.\s]', mo.group(), 1)[1]  # 切割获取没有序号的课程名称，如: 建筑材料
            allTopic['class'][flag_class] = {}

        # 题型
        if typeRegex.search(p):
            flag_tiXing = re.split(r'[\)）\s]', mo.group(), 1)[1]  # 切割获取没有括号的题型名称，如: 建筑材料_选择题
            allTopic['class'][flag_class][flag_tiXing] = []

        # 每一题
        if TiGan_Regex.search(p):
            if len(allTopic['class'][flag_class][flag_tiXing]) < 2:  # 如果这种题型还未标记起始题号，就记录起始题号
                allTopic['class'][flag_class][flag_tiXing].append(flag_id)
            else:  # 如果已经记录起始题号，就更新结束题号
                allTopic['class'][flag_class][flag_tiXing][1] = flag_id
            start = index  # |题干结束| 所在的行
            end = index + 1  # |题干结束|下一行，即答案所在行
            thisDataIndex = []

            while data[start].strip() and not flag_TiXing.search(data[start].strip()):  # 查找题目前一行的位置  str.strip(data[start])
                if len(thisDataIndex) == 0: thisDataIndex.append(start)  # 设置本题起始行号
                else: thisDataIndex[0] = start
                start -= 1

            if len(thisDataIndex) == 1: thisDataIndex.append(end)  # 设置本题结束行号
            else: thisDataIndex[-1] = end

            if flag_class in TiGan_range:
                TiGan_range[flag_class].append(thisDataIndex)
            else:
                TiGan_range[flag_class] = [thisDataIndex]

            flag_id += 1

    # print(TiGan_range)
    test_id = 0  # 标记每一题的唯一id
    for class_name in TiGan_range:
        # print(class_name)
        # 对每一题进行解析，得出题干、选项、题型、答案
        for test_item in TiGan_range[class_name]:
            thisData = {
                'id': test_id,
                'title': '',
                '题型': '',
                '选项': [],
                '正确答案': [],
                'class': class_name
            }
            test_id += 1

            now_options_index = 0  # 初始选项为A，不能处理超过 'Z' 的选项
            # print(test_item)  # 当前题的范围
            for i in range(test_item[0], test_item[1] + 1):
                if TiGan_Regex.search(data[i]):
                    continue
                # print(i, data[i])
                this_line_content = data[i].strip()
                this_option_content = get_now_option_regex(now_options_index).search(this_line_content)  # 获取下一选项所对应的正则表达式所匹配的内容
                this_option_content = this_option_content.group() if this_option_content else this_option_content  # 如果找到了，就取出找到的字符

                # print('当前匹配选项:', this_option_content)
                # print('当前行:', this_line_content)
                # print('当前选项正则:', get_now_option_regex(now_options_index).pattern)  #, end='\n\n')


                if this_option_content:
                    # 当前选项独占一行
                    if is_line_end(this_line_content, this_option_content):
                        thisData['选项'].append(this_option_content)
                        now_options_index += 1  # 选项指针加1
                    # 当前行有多个选项
                    else:
                        while this_option_content and not is_line_end(this_line_content, this_option_content):
                            # print('当前行:', this_line_content, end='\n\n')
                            if now_options_index > 0:
                                # print('当前行:', this_line_content)
                                # print('当前选项:', this_option_content)
                                # print('下一选项正则规则:', get_now_option_regex(now_options_index).pattern)
                                this_option_content = get_now_option_regex(now_options_index).search(this_line_content)  # 获取下一选项所对应的正则表达式所匹配的内容
                                this_option_content = this_option_content.group() if this_option_content else this_option_content
                                # print('下一选项:', this_option_content, end='\n\n')

                            if now_options_index == 0:  # 如果当前是 A 选项，就把 A 选项之前的数据追加到题目中
                                thisData['title'] += this_line_content.split(this_option_content)[0]

                            if this_option_content:
                                thisData['选项'].append(this_option_content)
                                now_options_index += 1  # 选项指针加1

                elif not i == test_item[1]:
                    thisData['title'] += this_line_content

            daAn = trueAnswer.findall(data[test_item[1]])
            thisData['正确答案'] = [item for item in daAn]

            if daAn:
                if len(daAn) > 1: thisData['题型'] = '多选'
                elif daAn[0] == '对' or daAn[0] == '错': thisData['题型'] = '判断'
                else: thisData['题型'] = '单选'
            else:
                print('致命: 当前题目没有匹配到正确答案', thisData)

            allTopic['data'].append(thisData)


    # 将统计数据以json格式存储，在R中可以直接加载此文件以作分析
    with open('输出文件/' + title + '.json', 'w', encoding='utf-8') as f:
        json.dump(allTopic, f, ensure_ascii=False, indent=2)


if __name__ == '__main__':
    cwd = os.getcwd()
    print('请选择需要解析的Word文档……')
    dlg = win32ui.CreateFileDialog(1) # 参数 1 表示打开文件对话框
    dlg.SetOFNInitialDir(cwd) # 设置打开文件对话框中的初始显示目录
    dlg.DoModal()
    filePath = dlg.GetPathName()
    if not filePath: sys.exit() # 未选择文件时退出程序
    print('正在解析，请稍候……')

    title = os.path.basename(filePath).split('.')[0]
    # 若为doc，则转为docx
    while filePath.endswith('.doc'):
        word = wc.Dispatch("Word.Application")
        doc = word.Documents.Open(filePath)
        doc.SaveAs(cwd + '/题库文件/_' + title, 16)
        doc.Close()
        word.Quit()
        filePath = '题库文件/_' + title + '.docx'

    if filePath.endswith('docx'):
        data = readerDocx(filePath)
        decode(data, title)
        ctypes.windll.user32.MessageBoxW(0, "提取成功", "OK", 64 + 4096)
    else: ctypes.windll.user32.MessageBoxW(0, "仅支持 \'.doc\' 格式和 \'.docx\' 格式", "OK", 64 + 4096)
